#ifndef DHD_SOCKET_IO_H
#define DHD_SOCKET_IO_H

#include <vector>
#include <stack>
#include <exception>
#include <stdint.h>

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/function.hpp>
#include <boost/utility.hpp>

#include "buffers.h"
#include "refcount.h"


namespace dhd
{
   // Use socket reference so we can hand an existing TCP socket over to the
   // SSL instance.
   typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> ssl_socket;
   
   using   boost::asio::ip::tcp;
   
   static const int MAX_MSG_LEN = 64 * 1024;
   
   typedef boost::function< void (uint16_t, PBuffer, PBuffer) >  MessageArrivedCallback;
   typedef boost::function< void (const char *, bool) >          IOErrorCallback;
   
   
   struct BadProtocol : std::exception
   {
       const char * static_msg;
       bool         reconnect;
       BadProtocol( const char * m, bool _reconnect = true ) : static_msg(m), reconnect(_reconnect) {}
       char const * what() const throw() { return static_msg; }
   };

   
   // This derives form Refcountable to simplify deletion whilst outstanding async I/O requests
   // are pending. The async I/O requests contain an embedded reference.
   // 
   class SocketIO : public Refcounted, public boost::noncopyable
   {
       struct __attribute__((__packed__)) TxLens 
       {
           uint16_t header_len; 
           uint16_t buffer_len; 
           uint16_t request_id; 
       };
       
       typedef std::vector< PBuffer >                   BufferVector;
       typedef std::vector< boost::asio::const_buffer > TransmitVector;
       typedef std::vector< TxLens * >                  LenVector;
       typedef std::stack< TxLens * >                   LenStack;
       
       boost::mutex   q_mutex;
       
       //-----------------
       // Write
       // 
       tcp::socket    * sock;
       ssl_socket     * ssl_sock;
       
       BufferVector   * queued_buffs;
       BufferVector   * tx_buffs;
       
       LenVector      * queued_lens;
       LenVector      * tx_lens;
       LenStack         len_stack;
       
       TransmitVector * queued_vec;
       TransmitVector * tx_vec;
       
       bool disconnected; // True if errors have been encountered and I/O completions should be ignored
       
       bool transmit_pending;
       
       bool delete_sockets;
       
       void report_error( const char * msg, bool reconnect );
       
       void begin_transmit();
       
       void on_async_write_complete( const boost::system::error_code& error, std::size_t bytes_transferred, boost::intrusive_ptr<SocketIO> ref );
       
       //-----------------
       // Read
       //
       uint16_t                msg_lens[3]; // for async_read
       MessageArrivedCallback  on_arrive_callback;
       IOErrorCallback         io_error_callback;
       
       void on_async_read_complete( const boost::system::error_code& error, std::size_t bytes_transferred, boost::intrusive_ptr<SocketIO> ref );
       
       void async_read_next();
       
       void on_zero_refcount() { delete this; }
       
     public:
       
       SocketIO( tcp::socket            * _sock, 
                 ssl_socket             * _ssl_sock,
                 MessageArrivedCallback   _cb, // Should throw BadProtocol on error
                 IOErrorCallback          _ecb,
                 bool                     _delete_sockets = false );
       
       ~SocketIO();
       
       // 'buffer' may be NULL
       void enqueue( uint16_t request_id, PBuffer header, PBuffer buffer );
   };
   
   
   typedef boost::intrusive_ptr<SocketIO> PSocketIO;
   
   
}// end namespace dhd

#endif